50. 度量指标(Metrics)

Spring Boot执行器包含一个支持'gauge'和'counter'级别的度量指标服务,'gauge'记录一个单一值,'counter'记录一个增量(增加或减少)。同时,Spring Boot提供一个PublicMetrics接口,你可以实现它,从而暴露以上两种机制不能记录的指标,具体参考SystemPublicMetrics

所有HTTP请求的指标都被自动记录,所以如果点击metrics端点,你可能会看到类似以下的响应:

{
    "counter.status.200.root": 20,
    "counter.status.200.metrics": 3,
    "counter.status.200.star-star": 5,
    "counter.status.401.root": 4,
    "gauge.response.star-star": 6,
    "gauge.response.root": 2,
    "gauge.response.metrics": 3,
    "classes": 5808,
    "classes.loaded": 5808,
    "classes.unloaded": 0,
    "heap": 3728384,
    "heap.committed": 986624,
    "heap.init": 262144,
    "heap.used": 52765,
    "mem": 986624,
    "mem.free": 933858,
    "processors": 8,
    "threads": 15,
    "threads.daemon": 11,
    "threads.peak": 15,
    "uptime": 494836,
    "instance.uptime": 489782,
    "datasource.primary.active": 5,
    "datasource.primary.usage": 0.25
}

此处,我们可以看到基本的memoryheapclass loadingprocessorthread pool信息,连同一些HTTP指标。在该实例中,root('/'),/metrics URLs分别返回20次,3HTTP 200响应,同时可以看到root URL返回了4HTTP 401(unauthorized)响应。双星号(star-star)来自于被Spring MVC /**匹配到的请求(通常为静态资源)。

gauge展示了一个请求的最后响应时间,所以root的最后请求响应耗时2毫秒/metrics耗时3毫秒

在该示例中,我们实际是通过HTTP的/metrics路径访问该端点的,这也就是响应中出现metrics的原因。

50.1 系统指标

Spring Boot会暴露以下系统指标: - 系统内存总量(mem),单位:KB - 空闲内存数量(mem.free),单位:KB - 处理器数量(processors) - 系统正常运行时间(uptime),单位:毫秒 - 应用上下文(应用实例)正常运行时间(instance.uptime),单位:毫秒 - 系统平均负载(systemload.average) - 堆信息(heapheap.committedheap.initheap.used),单位:KB - 线程信息(threadsthread.peakthead.daemon) - 类加载信息(classesclasses.loadedclasses.unloaded) - 垃圾收集信息(gc.xxx.count, gc.xxx.time

50.2 数据源指标

Spring Boot会为应用中定义的每个支持的DataSource暴露以下指标: - 活动连接数(datasource.xxx.active) - 连接池当前使用情况(datasource.xxx.usage

所有数据源指标共用datasoure.前缀,该前缀适用于每个数据源: - 如果是主数据源(唯一可用的数据源或注解@Primary的数据源)前缀为datasource.primary。 - 如果数据源bean名称以DataSource结尾,前缀就是bean的名称去掉DataSource的部分(比如,batchDataSource的前缀是datasource.batch)。 - 其他情况使用bean的名称作为前缀。

通过注册自定义版本的DataSourcePublicMetrics bean,你可以覆盖部分或全部的默认行为。Spring Boot默认提供支持所有数据源的元数据,如果喜欢的数据源恰好不被支持,你可以添加其他的DataSourcePoolMetadataProvider beans,具体参考DataSourcePoolMetadataProvidersConfiguration

50.3 缓存指标

Spring Boot会为应用中定义的每个支持的缓存暴露以下指标: - cache当前大小(cache.xxx.size) - 命中率(cache.xxx.hit.ratio) - 丢失率(cache.xxx.miss.ratio

缓存提供商没有以一致的方式暴露命中/丢失率,有些暴露的是聚合(aggregated)值(比如,自从统计清理后的命中率),而其他暴露的是时序(temporal)值 (比如,最后一秒的命中率),具体查看缓存提供商的文档。

如果两个不同的缓存管理器恰巧定义了相同的缓存,缓存name将以CacheManager bean的name作为前缀。

注册自定义版本的CachePublicMetrics可以部分或全部覆盖这些默认值,Spring Boot默认为EhCache,Hazelcast,Infinispan,JCache和Guava提供统计。如果喜欢的缓存库没被支持,你可以添加其他CacheStatisticsProvider beans,具体可参考CacheStatisticsAutoConfiguration

50.4 Tomcat session指标

如果你使用Tomcat作为内嵌的servlet容器,Spring Boot将自动暴露session指标, httpsessions.activehttpsessions.max分别提供活动的和最大的session数量。

50.5 记录自己的指标

CounterServiceGaugeService注入到你的bean中可以记录自己的度量指标:CounterService暴露incrementdecrementreset方法;GaugeService提供一个submit方法。

下面是一个简单的示例,它记录了方法调用的次数:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.CounterService;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final CounterService counterService;

    @Autowired
    public MyService(CounterService counterService) {
        this.counterService = counterService;
    }

    public void exampleMethod() {
        this.counterService.increment("services.system.myservice.invoked");
    }

}

你可以将任何字符串用作度量指标的名称,但最好遵循所选存储/图形技术的指南,Matt Aimonetti’s Blog中有一些好的关于Graphite的指南。

50.6 添加自己的公共指标

只要注册其他的PublicMetrics实现beans,你就可以添加其他的度量指标,比如计算metrics端点每次调用的次数。默认情况下,端点会聚合所有这样的beans,通过定义自己的MetricsEndpoint可以轻易改变这种情况。

50.7 使用Java8的特性

Spring Boot提供的GaugeServiceCounterService默认实现依赖于你使用的Java版本。如果使用Java8(或更高版本),Spring Boot将实现切换为一个高性能版本,该版本优化了写速度,底层使用原子内存buffers,而不是通过不可变但相对昂贵的Metric<?>类型(跟基于仓库的实现相比,counters大概快5倍,gauges大概快2倍)。对于Java7,Dropwizard指标服务也是很有效的(使用了某些Java8并发库),但它不记录指标值的时间戳。如果需要关注指标采集的性能,建议你使用高性能的选项,并不要频繁读取指标信息,这样写入会本地缓存,只有在需要时读取。

如果使用Java8或Dropwizard,Spring Boot默认不会使用老的MetricRepository和它的InMemoryMetricRepository实现。

50.8 指标写入,导出和聚合

Spring Boot提供几个标记接口Exporter的实现,可用于将从内存buffers读取的指标复制到一个分析和展示它们的地方。实际上,如果提供一个实现MetricWriter接口(或GaugeWriter用于简单场景)且注解@ExportMetricWriter@Bean,它将自动挂钩一个Exporter并每5秒反馈下指标更新(通过spring.metrics.export.delay-millis配置)。此外,你定义的所有注解@ExportMetricReaderMetricReader,它们的值将被默认exporter导出。

默认exporter是一个MetricCopyExporter,它会优化自己不去复制那些从上次调用以来没有变化的值(设置spring.metrics.export.send-latest标识可以关闭该优化)。注意Dropwizard MetricRegistry不支持时间戳,所以如果你使用Dropwizard指标服务,该优化是不起作用的(每次都会复制全部指标)。

通过spring.metrics.export.*属性可以设置导出的触发器(delay-millisincludesexcludessend-latest),特殊MetricWriters的值可以通过spring.metrics.export.triggers.<name>.*设置,此处<name>是bean的名称(或匹配bean名称的表达式)。

如果关闭默认的MetricRepository(比如使用Dropwizard指标服务),指标的自动导出将禁用。你可以通过声明自定义类型的MetricReader并注解@ExportMetricReader来获取相同功能。

50.8.1 示例: 导出到Redis

如果提供一个RedisMetricRepository类型的@Bean并注解@ExportMetricWriter,指标将导出到Redis缓存完成聚合。RedisMetricRepository有两个重要参数用于配置实现这样的目的:prefixkey(传递给构造器)。最好使用应用实例唯一的前缀(比如,使用一个随机值及应用的逻辑name,这样可以关联相同应用的其他实例)。“key”用来保持所有指标name的全局索引,所以它应该全局唯一,不管这对于你的应用意味着什么(比如,相同系统的两个实例可以共享一个Redis缓存,如果它们有不同的keys)。

示例:

@Bean
@ExportMetricWriter
MetricWriter metricWriter(MetricExportProperties export) {
    return new RedisMetricRepository(connectionFactory,
      export.getRedis().getPrefix(), export.getRedis().getKey());
}

application.properties

spring.metrics.export.redis.prefix: metrics.mysystem.${spring.application.name:application}.${random.value:0000}
spring.metrics.export.redis.key: keys.metrics.mysystem

前缀最后由应用名和id组成,所以它可以用来标识具有相同逻辑名的processes分组。

设置keyprefix都是非常重要的。key用于所有的仓库操作,并可以被多个仓库共享。如果多个仓库共享一个key(比如你需要聚合它们的时候),你通常有一个只读“master”仓库,它有一个简短的但可辨识的前缀(比如metrics.mysystem),还有很多只写的仓库,这些仓库以master前缀开头(比如以上示例中为metrics.mysystem.*)。这样从一个"master"仓库读取所有keys是相当高效的,但使用较长的前缀读取一个子集就比较低效了(比如使用一个写仓库)。

以上示例使用MetricExportProperties去注入和提取key和前缀,这是Spring Boot提供的便利设施,用于配置合适的默认值,你也可以自己设值。

50.8.2 示例: 导出到Open TSDB

如果提供一个OpenTsdbGaugeWriter类型的@Bean并注解@ExportMetricWriter,指标将导出到Open TSDB 完成聚合。OpenTsdbGaugeWriter有一个url属性,你需要将它设置为Open TSDB的“/put”端点,比如localhost:4242/api/put。它还有个namingStrategy,你可以自定义或配置以使指标匹配服务器上你需要的数据结构。默认它只传递指标名作为Open TSDB指标名,添加domain标签(值为org.springframework.metrics)和process(值为命名策略的对象hash值)。因此,在运行应用并产生一些指标后,你可以在TSD UI查看这些指标(默认路径为localhost:4242)。

示例:

curl localhost:4242/api/query?start=1h-ago&m=max:counter.status.200.root
[
    {
        "metric": "counter.status.200.root",
        "tags": {
            "domain": "org.springframework.metrics",
            "process": "b968a76"
        },
        "aggregateTags": [],
        "dps": {
            "1430492872": 2,
            "1430492875": 6
        }
    }
]

50.8.3 示例: 导出到Statsd

想要将指标导出到Statsd,首先你需要确定添加了com.timgroup:java-statsd-client依赖(Spring Boot为它提供了依赖管理),然后将spring.metrics.export.statsd.host属性添加到application.properties文件中,连接将在8125端口建立,除非设置spring.metrics.export.statsd.port对默认值进行覆盖。使用spring.metrics.export.statsd.prefix可以设置自定义前缀,此外,你可以提供一个StatsdMetricWriter类型的@Bean并注解@ExportMetricWriter

@Value("${spring.application.name:application}.${random.value:0000}")
private String prefix = "metrics";

@Bean
@ExportMetricWriter
MetricWriter metricWriter() {
    return new StatsdMetricWriter(prefix, "localhost", 8125);
}

50.8.4 示例: 导出到JMX

如果提供一个JmxMetricWriter类型并注解@ExportMetricWriter@Bean,指标将作为MBeans暴露到本地服务器(只要开启,Spring Boot JMX自动配置会提供MBeanExporter)。

示例:

@Bean
@ExportMetricWriter
MetricWriter metricWriter(MBeanExporter exporter) {
    return new JmxMetricWriter(exporter);
}

每个指标都暴露为单独的MBean,你可以将ObjectNamingStrategy注入JmxMetricWriter来指定ObjectNames的格式。

50.9 聚合多个来源的指标

Spring Boot提供一个AggregateMetricReader,用于合并来自不同物理来源的指标。具有相同逻辑指标的来源只需将指标加上以句号分隔的前缀发布出去,reader会聚合它们(通过截取指标名并丢掉前缀),计数器被求和,所有东西(比如gauges)都采用最近的值。

这非常有用,特别是当有多个应用实例反馈数据到中央仓库(比如Redis),并且你想展示结果。推荐将MetricReaderPublicMetrics结果连接到/metrics端点。

示例:

@Autowired
private MetricExportProperties export;

@Bean
public PublicMetrics metricsAggregate() {
  return new MetricReaderPublicMetrics(aggregatesMetricReader());
}

private MetricReader globalMetricsForAggregation() {
  return new RedisMetricRepository(this.connectionFactory,
      this.export.getRedis().getAggregatePrefix(), this.export.getRedis().getKey());
}

private MetricReader aggregatesMetricReader() {
  AggregateMetricReader repository = new AggregateMetricReader(
      globalMetricsForAggregation());
  return repository;
}

上面的示例使用MetricExportProperties注入和提取key和前缀,这是Spring Boot提供的便利设施,并且默认值是合适的,它们是在MetricExportAutoConfiguration中设置的。

上面的MetricReaders不是@Beans,也没注解@ExportMetricReader,因为它们只收集和分析来自其他仓库的数据,不需要暴露自己的值。

50.10 Dropwizard指标

当你声明对io.dropwizard.metrics:metrics-core的依赖时,Spring Boot会创建一个默认的MetricRegistry bean。如果需要自定义,你可以注册自己的@Bean实例。使用Dropwizard ‘Metrics’ library的用户会发现Spring Boot指标自动发布到com.codahale.metrics.MetricRegistry,来自MetricRegistry的指标也自动暴露到/metrics端点。

使用Dropwizard指标时,默认的CounterServiceGaugeServiceDropwizardMetricServices替换,它是一个MetricRegistry的包装器(所以你可以@Autowired其中任意services,并像平常那么使用它)。通过使用恰当的前缀类型标记你的指标名可以创建特殊的Dropwizard指标服务(比如,gauges使用timer.*histogram.*,counters使用meter.*)。

50.11 消息渠道集成

如果存在名为metricsChannelMessageChannel bean,Spring Boot将创建一个MetricWriter将指标写入该渠道(channel)。writer自动挂钩一个exporter,所以全部指标值都会出现在渠道上, 订阅者就可以进行其他分析或动作(提供渠道和订阅者取决于你)。